<?php

	namespace org\tekuna\framework\action;

	use \Exception;

	use org\tekuna\base\Tekuna;
	
	use org\tekuna\core\application\Application;
	use org\tekuna\core\context\Context;
	use org\tekuna\core\configuration\ConfigurationElement;
	use org\tekuna\core\configuration\ConfigurationException;

	use org\tekuna\framework\RequestDispatchException;

	
	/**
	 * This class processes a already matched component to find the Action for the
	 * current request. The action is matched within the component's configuration
	 * element. For the matching a URL pattern or a RegEx pattern can be used.
	 */
	class HttpActionProcessor {

		private
			$objContext = NULL,
			$objComponentElement = NULL,
			$arrActions = array(),
		
			$objLogger = NULL,

			$sMatchedComponentBaseUrl = NULL,
			$sMatchedActionUrl = NULL,
			$arrMatchedUrlParts = array(),
			$sMatchedPattern = NULL,
			$sMatchedRegex = NULL;
			
		
		/**
		 * Construct a new HttpActionProcessor
		 * 
		 * @param Context $objContext the application context
		 * @param ConfigurationElement $objComponentElement the already matched component element
		 */
		public function __construct(Context $objContext, ConfigurationElement $objComponentElement) {

			$this -> objContext = $objContext;
			$this -> objComponentElement = $objComponentElement;

			// TODO: some validation of the actions
			$this -> arrActions = $objComponentElement -> getAllChildElements('framework', 'action');

			$this -> objLogger = Tekuna :: getLogger(__CLASS__);
		}

		
		/**
		 * This method runs the matching for all actions within the component.
		 * 
		 * @return ConfigurationElement the matched action element
		 * @throws ActionExceptiond when anything goes wrong
		 * @throws RequestDispatchExeption when none of the actions 
		 *         within the component matches the current request
		 */
		public function getMatchingHttpAction() {

			foreach ($this -> arrActions as $objAction) {

				try {

					if ($this -> isMatchingHttpAction($objAction)) {

						return $objAction;
					}
				}
				catch (Exception $objException) {

					throw new ActionException("Error while matching HTTP action ". trim($objAction -> __toString()), -1, $objException);
				}
			}

			throw new RequestDispatchException("No matching HTTP action found.");
		}

		
		protected function isMatchingHttpAction(ConfigurationElement $objActionElement) {

			// get the full action to match
			$sUrlToMatch = $this -> objContext -> getRequest() -> getRequestAction();

			// remove the already matched base url
			$sComponentBaseUrl = $this -> objComponentElement -> getAttribute('framework', 'baseUrl') -> getValue();
			$sUrlToMatch = substr($sUrlToMatch, strlen($sComponentBaseUrl));

			$sActionPattern = NULL;
			if ($objActionElement -> hasAttribute('framework', 'pattern')) {

				$sActionPattern = $objActionElement -> getAttribute('framework', 'pattern') -> getValue();
				$this -> objLogger -> debug("Using url pattern '$sActionPattern' to try match action.");
				$sPattern = self :: transformPatternToPcre($sActionPattern);
				$this -> objLogger -> debug("Transformed the url pattern to pcre '$sPattern'.");
			}
			elseif ($objActionElement -> hasAttribute('framework', 'regex')) {

				$sPattern = $objActionElement -> getAttribute('framework', 'regex') -> getValue();
				$this -> objLogger -> debug("Using regex pattern '$sPattern' to try match action.");
			}
			else {

				throw new ConfigurationException("Either 'pattern' or 'regex' parameter required for action: ". trim($objActionElement -> __toString()));
			}

			// perform the matching
			if (preg_match($sPattern, $sUrlToMatch, $arrResults)) {

				$this -> sMatchedComponentBaseUrl = $sComponentBaseUrl;
				$this -> sMatchedActionUrl = $sUrlToMatch;
				$this -> arrMatchedUrlParts	= $arrResults;
				$this -> sMatchedPattern = $sActionPattern;
				$this -> sMatchedRegex = $sPattern;

				return true;
			}

			return false;
		}
		
		public function getMatchedComponentBaseUrl() {
			
			return $this -> sMatchedComponentBaseUrl;
		}
		
		public function getMatchedActionUrl() {
			
			return $this -> sMatchedActionUrl;
		}
		
		public function getMatchedUrlParts() {
			
			return $this -> arrMatchedUrlParts;
		}
		
		public function getMatchedPattern() {
			
			return $this -> sMatchedPattern;
		}
		
		public function getMatchedRegex() {
			
			return $this -> sMatchedRegex;
		}
		

		/**
		 * Transform a given URL pattern to RegEx pattern
		 * 
		 * @param $sUrlPattern
		 */
		public static function transformPatternToPcre($sUrlPattern) {

			$sPcrePattern = $sUrlPattern;

			// escape special characters except (, ), [, ]
			$sPcrePattern = str_replace('~', '\~', $sPcrePattern);
			$sPcrePattern = str_replace('\\', '\\\\', $sPcrePattern);
			$sPcrePattern = str_replace('^', '\^', $sPcrePattern);
			$sPcrePattern = str_replace('$', '\$', $sPcrePattern);
			$sPcrePattern = str_replace('|', '\|', $sPcrePattern);
			$sPcrePattern = str_replace('?', '\?', $sPcrePattern);
			$sPcrePattern = str_replace('*', '\*', $sPcrePattern);
			$sPcrePattern = str_replace('+', '\+', $sPcrePattern);
			$sPcrePattern = str_replace('{', '\{', $sPcrePattern);
			$sPcrePattern = str_replace('}', '\}', $sPcrePattern);
			$sPcrePattern = str_replace('.', '\.', $sPcrePattern);

			// match optional parts
			$sPcrePattern = preg_replace('~\[([^\]]+)\]~', '(\\1)?', $sPcrePattern);

			// match multi-level parts; do not use \w here because of its locale-specific behaviour
			$sPcrePattern = preg_replace('~\(\(([a-zA-Z0-9_]+)\)\)~', '(?P<\\1>.+?)', $sPcrePattern);

			// match single-level parts (and ignoring already matched optional parts...)
			$sPcrePattern = preg_replace('~\(([a-zA-Z0-9_]+)\)(?!\?)~', '(?P<\\1>[^/]+?)', $sPcrePattern);

			// add pattern delimiters and anchors
			$sPcrePattern = '~^'. $sPcrePattern .'$~';

			return $sPcrePattern;
		}
	}

